/*
 *
 *  *    Copyright © OpenAtom Foundation.
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *         http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.inspur.edp.commonmodel.engine.core.data.serializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.GspBusinessEntity;
import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.api.manager.serialize.CefSerializeContext;
import com.inspur.edp.cef.api.manager.serialize.JsonFormatType;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementDataType;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.designtime.api.util.MetadataUtil;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.spi.jsonser.base.StringUtils;
import com.inspur.edp.cef.spi.jsonser.entity.AbstractEntitySerializerItem;
import com.inspur.edp.cef.spi.jsonser.util.SerializerUtil;
import com.inspur.edp.commonmodel.engine.api.data.AssociationInfo;
import com.inspur.edp.commonmodel.engine.core.common.CMUtil;
import com.inspur.edp.commonmodel.engine.core.exception.CMEngineCoreException;
import com.inspur.edp.commonmodel.engine.core.exception.ErrorCodes;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import lombok.var;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class EngineSerializerItem extends AbstractEntitySerializerItem {

    protected IGspCommonObject node;

    public EngineSerializerItem(IGspCommonObject node) {
        this();
        this.node = node;
    }

    public EngineSerializerItem() {
    }

    private Object readAssociation(
            GspAssociation asso, JsonParser reader, DeserializationContext serializer) {
        if (reader.getCurrentToken() == JsonToken.VALUE_NULL) {
            return null;
        }
        AssociationInfo info = createAssociationInfo(asso);
        try {
            reader.nextToken();
            while (reader.getCurrentToken() == JsonToken.FIELD_NAME) {
                String propertyName = reader.getValueAsString();
                reader.nextToken();
                if (asso.getBelongElement().getLabelID().equalsIgnoreCase(propertyName)) {
                    info.setValue(asso.getBelongElement().getLabelID(), readString(reader));
                } else {
                    IGspCommonElement element = CMUtil.checkRefElementExists(asso, propertyName);
                    info.setValue(element.getLabelID(), readElementInfo(element, reader, serializer));
                }
                reader.nextToken();
            }
//      reader.nextToken();
            return info;
        } catch (IOException e) {
            throw new CMEngineCoreException(e);
        }
    }

    protected AssociationInfo createAssociationInfo(GspAssociation association) {
        return new AssociationInfo(association);
    }

    private void writeAssociation(
            GspAssociation asso,
            String propName,
            AssociationInfo assoValue,
            JsonGenerator writer,
            SerializerProvider serializer) {
        try {
            writer.writeFieldName(StringUtils.toCamelCase(propName));
            if (assoValue == null) {
                writer.writeNull();
            } else {
                writer.writeStartObject();
                writeString(writer, assoValue.getValue(asso.getBelongElement().getLabelID()),
                        asso.getBelongElement().getLabelID(), serializer);
                for (IGspCommonField refElement : asso.getRefElementCollection()) {
                    writeElementInfo(
                            refElement, assoValue.getValue(refElement.getLabelID()), writer, serializer);
                }
                writer.writeEndObject();
            }
        } catch (IOException e) {
            throw new CMEngineCoreException(e);
        }
    }

    @Override
    public void writeEntityBasicInfo(JsonGenerator writer, IEntityData data,
                                     SerializerProvider serializer) {
        getElements().forEach(
                element -> {
                    Object value = data.getValue(element.getLabelID());
                    if (value instanceof Date) {
                        if (element.getMDataType() == GspElementDataType.Date)
                            this.writeDate(writer, value, element.getLabelID(), serializer);
                        else
                            this.writeDateTime(writer, value, element.getLabelID(), serializer);
                    } else if (element.getObjectType() == GspElementObjectType.Association) {
                        if (getCefSerializeContext().getJsonFormatType() != JsonFormatType.Tiled)
                            this.writeAssociation(writer, value, element.getLabelID(), serializer);
                        else
                            writeTiledAssociation(writer, value, element, serializer, "");
                    } else {
                        this.writeBaseType(writer, value, element.getLabelID(), serializer);
                    }
                });
    }

    private void writeTiledAssociation(JsonGenerator writer, Object value, IGspCommonField element, SerializerProvider serializer, String prefix) {

        AssociationInfo info = (AssociationInfo) value;
        writeString(writer, info.getValue(), prefix + element.getLabelID(), serializer);
        GspAssociation association = element.getChildAssociations().get(0);
        for (IGspCommonField refEle : association.getRefElementCollection()) {
            if (refEle.getObjectType() != GspElementObjectType.Association) {
                switch (refEle.getMDataType()) {
                    case Date:
                        writeDate(writer, info.getValue(refEle.getLabelID()), prefix + refEle.getLabelID(), serializer);
                        break;
                    case DateTime:
                        writeDateTime(writer, info.getValue(refEle.getLabelID()), prefix + refEle.getLabelID(), serializer);
                        break;
                    default:
                        writeBaseType(writer, info.getValue(refEle.getLabelID()), prefix + refEle.getLabelID(), serializer);
                }
            } else {
                GspMetadata metadata = MetadataUtil.getCustomRTMetadata(refEle.getParentAssociation().getRefModelID());
                GspBusinessEntity be = (GspBusinessEntity) metadata.getContent();
                GspBizEntityObject obj = be
                        .getNode(element.getParentAssociation().getRefObjectCode());

                IGspCommonField belongField = null;
                for (IGspCommonField gspCommonField : obj.getContainElements()) {
                    if (gspCommonField.getID().equals(element.getRefElementId())) {
                        belongField = gspCommonField;
                        break;
                    }
                }
                writeTiledAssociation(writer, info.getValue(refEle.getLabelID()), belongField, serializer, prefix + refEle.getLabelID() + "_");

            }
        }

    }

    private void writeElementInfo(
            IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
        switch (element.getObjectType()) {
            case None:
                writeNormalElementInfo(element, value, writer, serializer);
                break;
            case Association:
                writeAssociationElementInfo(element, value, writer, serializer);
                break;
            case Enum:
                writeEnumElementInfo(element, value, writer, serializer);
                break;
            default:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1023, element.getLabelID(), String.valueOf(element.getObjectType()));
        }
    }

    private void writeEnumElementInfo(
            IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
        writeString(writer, value, element.getLabelID(), serializer);
    }

    private void writeAssociationElementInfo(
            IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
        // writeAssociation(writer, value, element.getLabelID(), serializer);
        GspAssociation asso = element.getChildAssociations().get(0);
        writeAssociation(asso, element.getLabelID(), (AssociationInfo) value, writer, serializer);
    }

    private void writeNormalElementInfo(
            IGspCommonField element, Object value, JsonGenerator writer, SerializerProvider serializer) {
        switch (element.getMDataType()) {
            case String:
            case Text:
                writeString(writer, value, element.getLabelID(), serializer);
                break;
            case Integer:
                writeInt(writer, value, element.getLabelID(), serializer);
                break;
            case Decimal:
                writeDecimal(writer, value, element.getLabelID(), serializer);
                break;
            case Boolean:
                writeBool(writer, value, element.getLabelID(), serializer);
                break;
            case Date:
                writeDate(writer, value, element.getLabelID(), serializer);
                break;
            case DateTime:
                writeDateTime(writer, value, element.getLabelID(), serializer);
                break;
            case Binary:
                writeBytes(writer, value, element.getLabelID(), serializer);
                break;
            case Geometry:
                writeGeometry(writer, value, element.getLabelID(), serializer);
                break;
            default:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1002, element.getLabelID(), String.valueOf(element.getMDataType()));
        }
    }

    @Override
    public boolean readEntityBasicInfo(
            JsonParser reader, DeserializationContext serializer, IEntityData data,
            String propertyName) {
        IGspCommonField element = getElements()
                .filter(item -> item.getLabelID().equalsIgnoreCase(propertyName))
                .findFirst().orElse(null);
        if (element == null) {
            return false;
        }
        data.setValue(element.getLabelID(), readElementInfo(element, reader, serializer));
        return true;
    }

    private Object readElementInfo(
            IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
        if (element.getIsUdt()) {
            //todo 解析型udt平铺格式暂不支持
            return readUdtElementInfo(element, reader, serializer);
        }

        switch (element.getObjectType()) {
            case None:
                return readNormalElementInfo(reader, serializer,element);
            case Association:
                return readAssociationElementInfo(element, reader, serializer);
            case Enum:
                return readEnumElementInfo(element, reader, serializer);
            default:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1023, element.getLabelID(), String.valueOf(element.getObjectType()));
        }
    }

    private Object readUdtElementInfo(IGspCommonField element, JsonParser reader,
                                      DeserializationContext serializer) {
        var udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
        return readNestedValue(udtConfigId, reader, serializer);
    }

    private Object readEnumElementInfo(
            IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
        return readString(reader);
    }

    private Object readAssociationElementInfo(
            IGspCommonField element, JsonParser reader, DeserializationContext serializer) {
        GspAssociation gspAssociation = null;
        if (element.getIsRefElement()) {
            GspMetadata metadata = MetadataUtil.getCustomRTMetadata(element.getParentAssociation().getRefModelID());
            GspBusinessEntity gspBusinessEntity = (GspBusinessEntity) metadata.getContent();
            GspBizEntityObject gspBizEntityObject = gspBusinessEntity
                    .getNode(element.getParentAssociation().getRefObjectCode());
            for (IGspCommonField gspCommonField : gspBizEntityObject.getContainElements()) {
                if (gspCommonField.getID().equals(element.getRefElementId())) {
                    gspAssociation = gspCommonField.getChildAssociations().get(0);
                    break;
                }
            }
        } else {
            gspAssociation = element.getChildAssociations().get(0);
        }
        return readAssociation(gspAssociation, reader, serializer);
    }

    private Object readNormalElementInfo(
            JsonParser reader, DeserializationContext serializer,IGspCommonField element) {
        switch (element.getMDataType()) {
            case String:
            case Text:
                return readString(reader);
            case Integer:
                return readInt(reader);
            case Decimal:
                return readDecimal(reader);
            case Boolean:
                return readBool(reader);
            case Date:
            case DateTime:
                return readDateTime(reader);
            case Binary:
                return readBytes(reader);
            case Geometry:
                return readGeometry(reader);
            default:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1002, element.getLabelID(), String.valueOf(element.getMDataType()));
        }
    }

    @Override
    public boolean writeModifyPropertyJson(
            JsonGenerator writer, String propertyName, Object value, SerializerProvider serializer) {
        //变更集的序列化, 0227与大家讨论后一致决定, 在序列化基类中对于seritem不处理的属性执行默认序列化, 所以此处
        //可以直接return false交由序列化基类处理.
        return false;
    }

    @Override
    public Object readModifyPropertyValue(JsonParser reader, DeserializationContext serializer,
                                          HashMap<String, Object> changeValues,
                                          RefObject<String> propertyName,
                                          RefObject<Boolean> hasRead,
                                          CefSerializeContext context) {
        //todo 临时处理，仅处理最简单场景。后续引入ResInfo后，基类可修改为CefEntityDataSerializerItem，此方法就不需要复写了，可以直接删除
        IGspCommonElement element = null;
        for (IGspCommonElement element1 : this.node.getAllElementList(true)) {
            if (element1.getLabelID().equalsIgnoreCase((String) propertyName.argvalue)) {
                element = element1;
                break;
            }
        }
        if (element == null) {
            hasRead.argvalue = false;
            return null;
        }
        hasRead.argvalue = true;
        propertyName.argvalue = element.getLabelID();
        if (element.getIsRefElement()) {
            IGspCommonField belongEle = element.getParentAssociation().getBelongElement();
            Object assoInfo = changeValues.get(belongEle.getLabelID());
            if (assoInfo == null)
                return null;
            Object value = readModifyByElement(reader, serializer, element, context, changeValues);
            ((AssociationInfo) assoInfo).setValue(element.getLabelID(), value);
        }

        return readModifyByElement(reader, serializer, element, context, changeValues);

    }

    private Object readModifyByElement(JsonParser reader, DeserializationContext serializer, IGspCommonField element, CefSerializeContext context, HashMap<String, Object> changeValues) {
        if (element.getIsUdt()) {

            String udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
            return readNestedChange(udtConfigId, reader, serializer);
        }
        switch (element.getObjectType()) {
            case None:
                return readNormalElementInfo(reader, serializer, element);
            case Association:
                return readAssociation(reader, serializer, element, context, changeValues);
            case Enum:
                return readEnumElement(reader, serializer, element);
            case DynamicProp:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1021, element.getLabelID());
        }
        throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1022, String.valueOf(element.getObjectType()), element.getLabelID());
    }

    private Object readAssociation(JsonParser reader, DeserializationContext serializer, IGspCommonField element, CefSerializeContext context, HashMap<String, Object> changeValues) {
        if (context.getJsonFormatType() == JsonFormatType.Tiled)
            return readTiledAssociation(reader, serializer, element, changeValues);
        return readAssociation(reader, serializer, element);
    }


    private Object readTiledAssociation(JsonParser reader, DeserializationContext serializer, IGspCommonField element, HashMap<String, Object> changeValues) {
        Objects.requireNonNull(element.getChildAssociations().get(0), element
                .getLabelID() + ".getChildAssociations().get(0)");
        AssociationInfo associationInfo = new AssociationInfo((GspAssociation) element.getChildAssociations().get(0));
        Object value = SerializerUtil.readString(reader);
        associationInfo.setValue(element.getLabelID(), value);
        changeValues.put(element.getLabelID(), associationInfo);
        return associationInfo;
    }

    @Override
    public Object readModifyPropertyValue(
            JsonParser reader,
            DeserializationContext serializer,
            RefObject<String> propertyName,
            RefObject<Boolean> hasRead) {
        var element = getElements()
                .filter(item -> item.getLabelID().equalsIgnoreCase(propertyName.argvalue)).findFirst();
        if (!element.isPresent()) {
            hasRead.argvalue = false;
            return null;
        }
        hasRead.argvalue = true;
        propertyName.argvalue = element.get().getLabelID();
        return readModifyByElement(reader, serializer, element.get());
    }

    private Stream<IGspCommonField> getElements() {
        Stream rez;
        if(node.getContainElements()!=null){
            rez = node.getContainElements().stream();
        }else{
            rez = Stream.empty();
        }
        Predicate<IGspCommonField> predicate = getElementPredicate();
        if (predicate != null) {
            rez = rez.filter(predicate);
        }
        return rez;
    }

//    private Stream<IGspCommonField> getElements(boolean containRef) {
//        Stream rez = node.getAllElementList(containRef).stream();
//        if (getElementPredicate() != null) {
//            rez = rez.filter(getElementPredicate());
//        }
//        return rez;
//    }

    private Object readModifyByElement(JsonParser reader,
                                       DeserializationContext serializer, IGspCommonField element) {
        if (element.getIsUdt()) {
            //todo 暂不支持平铺格式udt反序列化
            var udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
            return readNestedChange(udtConfigId, reader, serializer);
        }
        switch (element.getObjectType()) {
            case None:
                return readNormalElementInfo(reader, serializer, element);
            case Association:
                return readAssociation(reader, serializer, element);
            case Enum:
                return readEnumElement(reader, serializer, element);
            default:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1017, element.getLabelID());
        }
    }

    private Object readEnumElement(JsonParser reader, DeserializationContext serializer,
                                   IGspCommonField element) {
        return readString(reader);
    }

    private AssociationInfo readAssociation(JsonParser reader, DeserializationContext serializer,
                                            IGspCommonField element) {
        Objects.requireNonNull(element.getChildAssociations().get(0),
                element.getLabelID() + ".getChildAssociations().get(0)");

        if (reader.getCurrentToken() == JsonToken.VALUE_NULL) {
            return null;
        }
        if (reader.getCurrentToken() != JsonToken.START_OBJECT) {
            throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1020);
        }
        try {
            reader.nextToken();
            AssociationInfo rez = new AssociationInfo(element.getChildAssociations().get(0));

            while (reader.getCurrentToken() != JsonToken.END_OBJECT) {
                String propertyName = reader.getValueAsString();
                reader.nextToken();

                Object value;
                String actualName;
                if (element.getLabelID().equalsIgnoreCase(propertyName)) {
                    value = readString(reader);
                    actualName = element.getLabelID();
                } else {
                    IGspCommonField refEle = element.getChildAssociations().get(0)
                            .getRefElementCollection().stream()
                            .filter(item -> item.getLabelID().equalsIgnoreCase(propertyName)).findFirst()
                            .orElse(null);
                    if (refEle == null) {
                        throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1018, element.getLabelID(), propertyName);
                    }
                    value = readAssociationRefElement(reader, serializer, refEle);
                    actualName = refEle.getLabelID();
                }
                rez.setValue(actualName, value);
                reader.nextToken();
            }
            return rez;
        } catch (IOException e) {
            throw new CMEngineCoreException(e);
        }
    }

    private Object readAssociationRefElement(JsonParser reader,
                                             DeserializationContext serializer, IGspCommonField element) {
        if (element.getIsUdt()) {
            var udtConfigId = CMUtil.getUdtConfigId(element.getUdtID(), element.getUdtPkgName());
            return readNestedValue(udtConfigId, reader, serializer);
        }
        switch (element.getObjectType()) {
            case None:
                return readNormalElementInfo(reader, serializer, element);
            case Association:
                GspAssociation parentAssociation = element.getParentAssociation();
                GspMetadata refMetadata = MetadataUtil.getCustomRTMetadata(parentAssociation.getRefModelID());
                Objects.requireNonNull(refMetadata, parentAssociation.getBelongElement().getLabelID() + "associated metadata:" + parentAssociation.getRefModelID());
                Objects.requireNonNull(refMetadata.getContent(), parentAssociation.getBelongElement().getLabelID() + "associated metadata:" + parentAssociation.getRefModelID());
                GspBusinessEntity refBe = (GspBusinessEntity) refMetadata.getContent();
                GspBizEntityObject refNode =
                        org.springframework.util.StringUtils.isEmpty(parentAssociation.getRefObjectCode())
                                ? refBe.getMainObject() : refBe.getNode(parentAssociation.getRefObjectCode());
                IGspCommonElement referedElement = refNode.findElement(element.getRefElementId());
                Objects.requireNonNull(referedElement, element.getName() + "associated element not exist");
                return readAssociation(reader, serializer, referedElement);
            case Enum:
                return readEnumElement(reader, serializer, element);
            default:
                throw new CMEngineCoreException(ErrorCodes.CM_ENGINE_1017, element.getLabelID());
        }
    }

    protected Predicate<IGspCommonField> getElementPredicate() {
        return null;
    }
}
